//
//  NetworkManager.swift
//  Copyright © 2020 杨东宝. All rights reserved.
//

import Alamofire
import Foundation
import KakaJSON
import Moya
import SwiftyJSON
/// 超时时长
private var requestTimeOut: Double = 15
/// 成功数据的回调
typealias successCallback = ((Any) -> Void)
/// 失败的回调
typealias failedCallback = ((String) -> Void)
/// 网络错误的回调
typealias errorCallback = (() -> Void)

typealias MyEndpointClosure<T:TargetType> = ((T) -> (Endpoint))

/// 网络请求的基本设置,这里可以拿到是具体的哪个网络请求，可以在这里做一些设置

func getMyEndpointClosure<T:TargetType>() -> MyEndpointClosure<T> {
    
    let myEndpointClosure:MyEndpointClosure = { (target:T) -> Endpoint in
        /// 这里把endpoint重新构造一遍主要为了解决网络请求地址里面含有? 时无法解析的bug https://github.com/Moya/Moya/issues/1198
        let url = target.baseURL.absoluteString + target.path
        var task = target.task
        
        /*
         如果需要在每个请求中都添加类似token参数的参数请取消注释下面代码
         👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇👇
         */
        //        let additionalParameters = ["token":"888888"]
        //        let defaultEncoding = URLEncoding.default
        //        switch target.task {
        //        ///在你需要添加的请求方式中做修改就行，不用的case 可以删掉。。
        //        case .requestPlain:
        //            task = .requestParameters(parameters: additionalParameters, encoding: defaultEncoding)
        //        case .requestParameters(var parameters, let encoding):
        //            additionalParameters.forEach { parameters[$0.key] = $0.value }
        //            task = .requestParameters(parameters: parameters, encoding: encoding)
        //        default:
        //            break
        //        }
        /*
         👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆👆
         如果需要在每个请求中都添加类似token参数的参数请取消注释上面代码
         */
        
        let endpoint = Endpoint(
            url: url,
            sampleResponseClosure: { .networkResponse(200, target.sampleData) },
            method: target.method,
            task: task,
            httpHeaderFields: target.headers
        )
        requestTimeOut = 30 // 每次请求都会调用endpointClosure 到这里设置超时时长 也可单独每个接口设置
        return endpoint
    }
    
    return myEndpointClosure
}

/// 网络请求的设置
private let requestClosure = { (endpoint: Endpoint, done: MoyaProvider.RequestResultClosure) in
    do {
        var request = try endpoint.urlRequest()
        // 设置请求时长
        request.timeoutInterval = requestTimeOut
        // 打印请求参数
        if let requestData = request.httpBody {
            print("\(request.url!)" + "\n" + "\(request.httpMethod ?? "")" + "发送参数" + "\(String(data: request.httpBody!, encoding: String.Encoding.utf8) ?? "")")
        } else {
            print("\(request.url!)" + "\(String(describing: request.httpMethod))")
        }
        done(.success(request))
    } catch {
        done(.failure(MoyaError.underlying(error, nil)))
    }
}

/*   设置ssl
 let policies: [String: ServerTrustPolicy] = [
 "example.com": .pinPublicKeys(
 publicKeys: ServerTrustPolicy.publicKeysInBundle(),
 validateCertificateChain: true,
 validateHost: true
 )
 ]
 */

// 用Moya默认的Manager还是Alamofire的Manager看实际需求。HTTPS就要手动实现Manager了
// private public func defaultAlamofireManager() -> Manager {
//
//    let configuration = URLSessionConfiguration.default
//
//    configuration.httpAdditionalHeaders = Alamofire.SessionManager.defaultHTTPHeaders
//
//    let policies: [String: ServerTrustPolicy] = [
//        "ap.grtstar.cn": .disableEvaluation
//    ]
//    let manager = Alamofire.SessionManager(configuration: configuration,serverTrustPolicyManager: ServerTrustPolicyManager(policies: policies))
//
//    manager.startRequestsImmediately = false
//
//    return manager
// }

/// NetworkActivityPlugin插件用来监听网络请求，界面上做相应的展示
/// 但这里我没怎么用这个。。。 loading的逻辑直接放在网络处理里面了
private let networkPlugin = NetworkActivityPlugin.init { changeType, _ in
    print("networkPlugin \(changeType)")
    // targetType 是当前请求的基本信息
    switch changeType {
    case .began:
        print("开始请求网络")
        
    case .ended:
        print("结束")
    }
}

// https://github.com/Moya/Moya/blob/master/docs/Providers.md  参数使用说明
// stubClosure   用来延时发送网络请求

/// /网络请求发送的核心初始化方法，创建网络请求对象

func getProvider<T:TargetType>() -> MoyaProvider<T> {
    
    let Provider = MoyaProvider<T>(endpointClosure: getMyEndpointClosure(), requestClosure: requestClosure, plugins: [networkPlugin], trackInflights: false)
    return Provider
    
}

/// 最常用的网络请求，只需知道正确的结果无需其他操作时候用这个 (可以给调用的NetWorkReques方法的写参数默认值达到一样的效果,这里为解释方便做抽出来二次封装)
///
/// - Parameters:
///   - target: 网络请求
///   - completion: 请求成功的回调
func NetWorkRequest<T:TargetType>(_ target: T, completion: @escaping successCallback) {
    NetWorkRequest(target, completion: completion, failed: nil, errorResult: nil)
}

/// 需要知道成功或者失败的网络请求， 要知道code码为其他情况时候用这个 (可以给调用的NetWorkRequest方法的参数默认值达到一样的效果,这里为解释方便做抽出来二次封装)
///
/// - Parameters:
///   - target: 网络请求
///   - completion: 成功的回调
///   - failed: 请求失败的回调
func NetWorkRequest<T:TargetType>(_ target: T, completion: @escaping successCallback, failed: failedCallback?) {
    NetWorkRequest(target, completion: completion, failed: failed, errorResult: nil)
}
///  需要知道成功、失败、错误情况回调的网络请求   像结束下拉刷新各种情况都要判断
///
/// - Parameters:
///   - target: 网络请求
///   - completion: 成功
///   - failed: 失败
///   - error: 错误
@discardableResult // 当我们需要主动取消网络请求的时候可以用返回值Cancellable, 一般不用的话做忽略处理
func NetWorkRequest<T:TargetType>(_ target: T, completion: @escaping successCallback, failed: failedCallback?, errorResult: errorCallback?) -> Cancellable? {
    // 先判断网络是否有链接 没有的话直接返回--代码略
    if !UIDevice.isNetworkConnect {
        print("提示用户网络似乎出现了问题")
        return nil
    }
    DebugLog("请求的参数：\(target)");
    // 这里显示loading图
    return getProvider().request(target) { result in
        // 隐藏hud
        switch result {
        case let .success(response):
            do {
                let jsonData = try JSON(data: response.data)
                DebugLog("返回的参数：\(jsonData)");
                //            这里的completion和failed判断条件依据不同项目来做，为演示demo我把判断条件注释了，直接返回completion。
                // 解析数据
                let code = Int((JSON(jsonData)[RESULT_CODE].description))
                let message = (JSON(jsonData)[RESULT_MESSAGE].description)
                
                guard code == 0  else {
                    failed?(message)
                    return
                }
                
                if jsonData[RESULT_DATA].dictionaryObject != nil {
                    
                    let json = JSON(jsonData)[RESULT_DATA].dictionaryObject
                     completion(json!)
                    
                }else if jsonData[RESULT_DATA].arrayObject != nil{
                    let json = JSON(jsonData)[RESULT_DATA].arrayObject
                    completion(json!)
                }
                else{
                    let json = JSON(jsonData)[RESULT_DATA].description
                    completion(json)
                }
            } catch {}
        case let .failure(error):
            // 网络连接失败，提示用户
            DebugLog("网络连接失败\(error)")
            errorResult?()
        }
    }
}

/**
 有同学问可否把数据转模型也封装到网络请求中  下面的方法是大概的实现思路，仅供参考↓↓↓↓↓↓↓↓↓↓↓↓
 */

// 成功回调
typealias RequestSuccessCallback = ((_ model: Any?,_ pageInfor:Any?, _ message: String?, _ resposneStr: Any) -> Void)
// 失败回调
typealias RequestFailureCallback = ((_ code: Int?, _ message: String?) -> Void)

/// 带有模型转化的底层网络请求的基础方法    可与 179 行核心网络请求方法项目替换 唯一不同点是把数据转模型封装到了网络请求基类中
///  本方法只写了大概数据转模型的实现，具体逻辑根据业务实现。
/// - Parameters:
///   - target: 网络请求接口
///   - isHideFailAlert: 是否隐藏失败的弹框
///   - modelType: 数据转模型所需要的模型
///   - successCallback: 网络请求成功的回调 转好的模型返回出来
///   - failureCallback: 网络请求失败的回调
/// - Returns: 可取消网络请求的实例
@discardableResult
func NetWorkRequest<T: Convertible,T1:TargetType,T2: Convertible>(_ target: T1, isHideFailAlert: Bool = false, modelType: T.Type?,pageType: T2.Type? = nil, successCallback: RequestSuccessCallback?, failureCallback: RequestFailureCallback? = nil) -> Cancellable? {
    // 这里显示loading图
     DebugLog("请求的参数：\(target)");
    return getProvider().request(target) { result in
        // 隐藏hud
        switch result {
        case let .success(response):
            do {
                let jsonData = try JSON(data: response.data)
                DebugLog("返回的参数：\(jsonData)");
                // data里面不返回数据 只是简单的网络请求 无需转模型
                if jsonData[RESULT_DATA].dictionaryObject == nil, jsonData[RESULT_DATA].arrayObject == nil { // 返回字符串
                    successCallback?(jsonData[RESULT_DATA].string,nil, jsonData[RESULT_MESSAGE].stringValue, jsonData)
                    return
                }
                // 解析数据
                let code = Int((JSON(jsonData)[RESULT_CODE].description))
                let message = (JSON(jsonData)[RESULT_MESSAGE].description)
                guard code == 0  else {
                    failureCallback?(code,message)
                    return
                }
                if jsonData[RESULT_DATA].dictionaryObject != nil { // 字典转model
                    
                    let json1:String? = JSON(jsonData)[RESULT_DATA][RESULT_PAGE].description
                    
                    //判断是否包含页面信息，包含则说明带分页
                    if json1 != "null" {
                        
                        guard let pageModel = model(from:json1!, T2.self) else {
                            failureCallback?(code,message)
                            return
                        }
                        if  jsonData[RESULT_DATA][RESULT_LIST].arrayObject != nil { //数组转model
                            
                            let json2:String? = JSON(jsonData)[RESULT_DATA][RESULT_LIST].description
   
                            guard let array:[T] = modelArray(from: json2!, type: T.self) as? [T] else {
                                failureCallback?(code,message)
                            return }

                            successCallback?(array,pageModel,message,jsonData)
                            
                    }
                        
                        return
                }
                    
                    
                    let json3 = JSON(jsonData)[RESULT_DATA].description
                    
                    guard let anyModel = model(from:json3, T.self) else {
                        failureCallback?(code,message)
                        return
                    }
                    
                    successCallback?(anyModel,nil,message, jsonData)
                    
                } else if jsonData[RESULT_DATA].arrayObject != nil { // 数组转model
                    
                    let json = JSON(jsonData)[RESULT_DATA].arrayObject
                    guard let array:[T] = modelArray(from: json!, type: T.self) as? [T] else {
                        failureCallback?(code,message)
                        return }
                    successCallback?(array,nil,message,jsonData)
                    
                }
            } catch {}
        case .failure(_):
            // 网络连接失败，提示用户
            failureCallback?(nil, "网络连接失败")
        }
    }
}

/// 基于Alamofire,网络是否连接，，这个方法不建议放到这个类中,可以放在全局的工具类中判断网络链接情况
/// 用计算型属性是因为这样才会在获取isNetworkConnect时实时判断网络链接请求，如有更好的方法可以fork
extension UIDevice {
    static var isNetworkConnect: Bool {
        let network = NetworkReachabilityManager()
        return network?.isReachable ?? true // 无返回就默认网络已连接
    }
}
